package com.example.myapplication.widgets.calendar.work;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.example.myapplication.R;
import com.example.myapplication.utils.TimeUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * 月表格
 * 目前文字的基准轴都是直接写死字体大小*0.82f，虽然视觉上是没什么问题，但并不严谨，后期可以修改成计算方式
 */
public class MonthTableView extends View implements GestureDetector.OnGestureListener {

    // region UI属性，间距颜色等
    private float mWidth, mHeight, mTotalWidth, mTotalHeight; // 整个试图的宽高，UI全绘制后总的宽高
    private float mItemHorizontalCount = 7f; // 横线显示多少个方格item
    private float mItemWidth, mItemMinHeight = 150; // 每一个方格item的宽高，宽度取决于mItemHorizontalCount，高度存在最小值固定
    private boolean isItemMinHeightAverage; // 最小高度是否铺满整个视图
    private int mItemPadding = 3; // 方格item的内间距、两个内容item的间距
    private float mWorkWidth, mWorkHeight; // 内容的宽高，宽度 = ItemWidth - mItemPadding * 2，高度 = 文字高度 + mWorkPaddingVertical * 2
    private int mWorkPaddingHorizontal = 3; // 内容的横向内间距
    private int mWorkPaddingVertical = 6; // 内容的纵向间距
    private final int mStrokeWidth = 2; // 线宽
    private int mWeekTextSize = 33; // 星期文字大小
    private int mDateTextSize = 33; // 日期文字大小
    private int mWorkTextSize = 33; // 内容文字大小
    private int mDateTextMargin = 4; // 日期文字与边框的间距
    private int mBiggerWorkTextSize = mWorkTextSize; // 仅展示标题，并且显示两行时，标题一行显示的下的时候使用
    private int mWeekMarginVertical = 30;
    private int mWeekHeight; // 顶部第一行，周的高度
    private int mCircleRadius = (int) (mDateTextSize * 1.6);
    private boolean isShowEllipsis;
    private static final int TITLE_INTERVAL_TO_SUB_TITLE = 10;

    private int mBackgroundColor = Color.WHITE;
    private int mAxisBackgroundColor = Color.WHITE;
    private int mCircleColor = Color.GREEN;
    private int mCircleSelColor = Color.GREEN;
    private int mSelBackgroundColor = Color.WHITE;
    private int mWeekColor = Color.LTGRAY;
    private int mDateColor = Color.BLACK;
    private int mLineColor = Color.LTGRAY;

    private final int mEnableAlpha = 255;
    private int mDisableAlpha = 127;
    // endregion

    private final String[] mWeek = new String[]{"周一", "周二", "周三", "周四", "周五", "周六", "周日"};

    // 是否仅展示标题
    private boolean isOnlyShowTitle;
    private boolean isShowTip = true;
    private boolean isShowSubTitle = true;
    private int mYear, mMonth, mDay; //选中的日期
    private int mLines; // 行数
    private List<DateInfo> mDate;
    private Map<String, List<TimeWork>> mWorkMap; // 工作项，key为date

    private Paint mPaint;
    private Path mPathHorizontal, mPathVertical;
    private Map<String, Float> mPathXMap; // 记录每一根竖线的X轴，用于work绘制时设置X轴起点，key为week 周
    private Map<Integer, Float> mPathYMap; // 记录每一根横线的Y轴，用于work绘制时设置Y轴起点，key为位置索引

    // 手势、滚动事件
    private GestureDetector gestureDetector;
    private Scroller scroller;
    private float mXOffset, mYOffset;

    private IOnClickMonthWorkItemListener mClickWorkItemListener;
    private IOnScrollListener mScrollListener;

    public MonthTableView(Context context) {
        this(context, null);
    }

    public MonthTableView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MonthTableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public MonthTableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MonthTableView);
            mBackgroundColor = typedArray.getColor(R.styleable.MonthTableView_mtv_background, mBackgroundColor);
            mAxisBackgroundColor = typedArray.getColor(R.styleable.MonthTableView_mtv_axis_background, mBackgroundColor);
            mWeekColor = typedArray.getColor(R.styleable.MonthTableView_mtv_week_color, mWeekColor);
            mDateColor = typedArray.getColor(R.styleable.MonthTableView_mtv_date_color, mDateColor);
            mLineColor = typedArray.getColor(R.styleable.MonthTableView_mtv_line_color, mLineColor);
            mCircleColor = typedArray.getColor(R.styleable.MonthTableView_mtv_circle_color, mCircleColor);
            mCircleSelColor = typedArray.getColor(R.styleable.MonthTableView_mtv_circle_sel_color, mCircleSelColor);
            mSelBackgroundColor = typedArray.getColor(R.styleable.MonthTableView_mtv_sel_background_color, mSelBackgroundColor);

            mItemPadding = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_item_padding, mItemPadding);
            mWeekTextSize = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_week_size, mWeekTextSize);
            mDateTextSize = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_date_size, mDateTextSize);
            mWorkTextSize = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_work_size, mWorkTextSize);
            mBiggerWorkTextSize = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_bigger_work_size, mWorkTextSize);
            mDateTextMargin = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_date_margin, mDateTextMargin);
            mWeekMarginVertical = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_week_margin_vertical, mWeekMarginVertical);
            mCircleRadius = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_circle_radius, mCircleRadius);
            mItemMinHeight = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_item_height, (int) mItemMinHeight);
            isItemMinHeightAverage = typedArray.getBoolean(R.styleable.MonthTableView_mtv_item_height_is_average, isItemMinHeightAverage);
            mWorkPaddingHorizontal = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_work_padding_horizontal, mWorkPaddingHorizontal);
            mWorkPaddingVertical = typedArray.getDimensionPixelSize(R.styleable.MonthTableView_mtv_work_padding_vertical, mWorkPaddingVertical);

            mItemHorizontalCount = typedArray.getFloat(R.styleable.MonthTableView_mtv_item_visible_count, mItemHorizontalCount);
            isShowEllipsis = typedArray.getBoolean(R.styleable.MonthTableView_mtv_is_show_ellipsis, isShowEllipsis);
            isShowTip = typedArray.getBoolean(R.styleable.MonthTableView_mtv_is_show_tip, isShowTip);
            mDisableAlpha = typedArray.getInt(R.styleable.MonthTableView_mtv_disable_alpha, mDisableAlpha);
            typedArray.recycle();
        }

        mDate = new ArrayList<>();
        mPathHorizontal = new Path();
        mPathVertical = new Path();
        mWorkMap = new HashMap<>();
        mPathXMap = new HashMap<>();
        mPathYMap = new HashMap<>();

        mPaint = new Paint();
        mPaint.setAlpha(mEnableAlpha);
        mPaint.setColor(mDateColor);
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        mPaint.setTextSize(mWeekTextSize);
        mWeekHeight = (int) (mPaint.getTextSize() + mWeekMarginVertical * 2);

        mPaint.setTextSize(mWorkTextSize);
        if (isShowTip)
            mWorkHeight = (int) (mPaint.getTextSize() * 2 + mWorkPaddingVertical * 3);
        else
            mWorkHeight = (int) (mPaint.getTextSize() + mWorkPaddingVertical * 2);

        scroller = new Scroller(context);
        gestureDetector = new GestureDetector(context, this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mWidth = getWidth();
        mHeight = getHeight();
        mItemWidth = (int) ((mWidth - mStrokeWidth * Math.floor(mItemHorizontalCount +
                (Math.ceil(mItemHorizontalCount) == Math.floor(mItemHorizontalCount) ? -1 : 0))) / mItemHorizontalCount);
        mWorkWidth = mItemWidth - 2 * mItemPadding;
        if (mItemHorizontalCount == mWeek.length)
            mTotalWidth = mWidth; // 因为涉及到除法转int，丢失小数，所以如果用计算后的值重新计算总值，会有几像素偏差
        else
            mTotalWidth = mStrokeWidth * (mWeek.length - 1) + mItemWidth * mWeek.length;

        // 开启，则进行平分高度，表格铺满整个视图
        if (isItemMinHeightAverage && mLines > 0) {
            float itemMinHeight = (mHeight - mWeekHeight - mLines * mStrokeWidth) / mLines;
            if (mItemMinHeight < itemMinHeight)
                mItemMinHeight = itemMinHeight;
        }

        createPath();
    }

    // region 对外属性设置、数据赋值等方法
    public void setOnClickWorkItemListener(IOnClickMonthWorkItemListener clickWorkItemListener) {
        mClickWorkItemListener = clickWorkItemListener;
    }

    public void setOnScrollListener(IOnScrollListener onScrollListener) {
        mScrollListener = onScrollListener;
    }

    public void setShowTip(boolean showTip) {
        isShowTip = showTip;
        if (mPaint != null) {
            mPaint.setTextSize(mWorkTextSize);
            if (isShowTip)
                mWorkHeight = (int) (mPaint.getTextSize() * 2 + mWorkPaddingVertical * 3);
            else
                mWorkHeight = (int) (mPaint.getTextSize() + mWorkPaddingVertical * 2);
        }
    }

    public void setShowSubTitle(boolean showSubTitle) {
        isShowSubTitle = showSubTitle;
    }

    public void setOnlyShowTitle(boolean onlyShowTitle) {
        isOnlyShowTitle = onlyShowTitle;
    }

    public void clear() {
        mYOffset = 0;
        mWorkMap.clear();
        refresh();
    }

    public String[] setDate(String date) {
        date = date.replace("-", "/");
        String[] split = date.split("/");
        if (split.length == 3) {
            try {
                int year = Integer.parseInt(split[0]);
                int month = Integer.parseInt(split[1]);
                int day = Integer.parseInt(split[2]);
                return setDate(year, month, day);
            } catch (Exception ignored) {
            }
        }
        return null;
    }

    // 返回第一天和最后一天的日期
    public String[] setDate(int year, int month, int day) {
        String[] firstLastDate = new String[2];
        mYear = year;
        mMonth = month;
        mDay = day;
        mDate.clear();
        DateInfo dateInfo;
        int totalDay = TimeUtils.getTotalDayOfMonth(year, month);
        int firstDayOfWeek = TimeUtils.getDayOfWeekMonday(String.format(Locale.getDefault(), "%02d-%02d-%02d", year, month, 1));
        if (firstDayOfWeek != 1) {
            int preYear = year;
            int preMonth = month - 1;
            if (preMonth == 0) {
                preYear--;
                preMonth = 12;
            }
            int preMonthTotalDay = TimeUtils.getTotalDayOfMonth(preYear, preMonth);
            for (int i = 1; i < firstDayOfWeek; i++) {
                dateInfo = new DateInfo();
                dateInfo.day = preMonthTotalDay - firstDayOfWeek + i + 1;
                dateInfo.date = String.format(Locale.getDefault(), "%02d-%02d-%02d", preYear, preMonth, dateInfo.day);
                mDate.add(dateInfo);
            }
        }
        for (int i = 1; i <= totalDay; i++) {
            dateInfo = new DateInfo();
            dateInfo.day = i;
            dateInfo.date = String.format(Locale.getDefault(), "%02d-%02d-%02d", year, month, dateInfo.day);
            mDate.add(dateInfo);
        }
        int lastDayOfWeek = TimeUtils.getDayOfWeekMonday(String.format(Locale.getDefault(), "%02d-%02d-%02d", year, month, totalDay));
        if (lastDayOfWeek != mWeek.length) {
            int nextYear = year;
            int nextMonth = month + 1;
            if (nextMonth == 13) {
                nextYear++;
                nextMonth = 1;
            }
            for (int i = 1; i <= mWeek.length - lastDayOfWeek; i++) {
                dateInfo = new DateInfo();
                dateInfo.day = i;
                dateInfo.date = String.format(Locale.getDefault(), "%02d-%02d-%02d", nextYear, nextMonth, dateInfo.day);
                mDate.add(dateInfo);
            }
        }
        firstLastDate[0] = mDate.get(0).date;
        firstLastDate[1] = mDate.get(mDate.size() - 1).date;
        mLines = mDate.size() / 7;
        return firstLastDate;
    }

    public void setWorkList(List<TimeWork> workList) {
        mWorkMap.clear();
        addWorkList(workList);
    }

    public void addWorkList(List<TimeWork> workList) {
        if (workList != null && !workList.isEmpty()) {
            for (TimeWork timeWork : workList) {
                List<TimeWork> timeWorks = mWorkMap.get(timeWork.getDate());
                if (timeWorks == null) {
                    timeWorks = new ArrayList<>();
                    mWorkMap.put(timeWork.getDate(), timeWorks);
                }
                timeWorks.add(timeWork);
            }
        }
        refresh();
    }

    public synchronized void removeWork(String id) {
        if (!TextUtils.isEmpty(id) && !mWorkMap.isEmpty()) {
            Set<String> keySet = mWorkMap.keySet();
            List<TimeWork> timeWorkList;
            List<TimeWork> removeList = new ArrayList<>();
            for (String key : keySet) {
                timeWorkList = mWorkMap.get(key);
                removeList.clear();
                if (timeWorkList != null && !timeWorkList.isEmpty()) {
                    for (TimeWork timeWork : timeWorkList) {
                        if (id.equals(timeWork.getId())) {
                            removeList.add(timeWork);
                        }
                    }
                    if (!removeList.isEmpty())
                        timeWorkList.removeAll(removeList);
                }
            }
            refresh();
        }
    }

    // 0-1
    public void setDisableAlpha(float disableAlpha) {
        disableAlpha = Math.max(0, disableAlpha);
        disableAlpha = Math.min(1, disableAlpha);
        mDisableAlpha = (int) (disableAlpha * 255);
    }

    public Map<String, List<TimeWork>> getWorkData() {
        return mWorkMap;
    }

    // endregion

    private void refresh() {
        // 还没获取到宽高，则不需要进行绘制，也绘制不到正确的试图
        if (mTotalWidth == 0 || mTotalHeight == 0) return;
        createPath();
        // 重新测量高度之后，如果当前偏移的位置操作最大高度，则需要重新绘制各个轴的位置
        if (checkOffset())
            createPath();
        invalidate();
    }

    private void createPath() {
        mPathHorizontal.reset();
        mPathVertical.reset();

        // 测量横线及时间的位置
        // 固定绘制第一根线
        mPathHorizontal.moveTo(0, mWeekHeight + mStrokeWidth / 2f);
        mPathHorizontal.lineTo(mWidth, mWeekHeight + mStrokeWidth / 2f);
        mPathYMap.put(0, mWeekHeight + mStrokeWidth / 2 + mYOffset);

        float y = mWeekHeight + mYOffset;
        float curY;
        int maxCount;
        DateInfo dateInfo;
        List<TimeWork> timeWorks;
        for (int i = 1; i <= mDate.size() / mWeek.length; i++) {
            maxCount = 0;
            for (int j = 0; j < mWeek.length; j++) {
                int index = (i - 1) * mWeek.length + j;
                dateInfo = mDate.get(index);
                timeWorks = mWorkMap.get(dateInfo.date);
                if (timeWorks != null)
                    maxCount = Math.max(maxCount, timeWorks.size());
            }
            curY = maxCount * (mWorkHeight + mItemPadding) + mItemPadding;
            if (curY < mItemMinHeight)
                curY = mItemMinHeight;
            curY += mStrokeWidth;
            y += curY;

            // 记录横线坐标
            mPathYMap.put(i, y);

            if (y > mHeight) {
                // 如果超出屏幕可视范围，则不用继续绘制了
                continue;
            } else if (y <= mWeekHeight) continue;
            mPathHorizontal.moveTo(0, y);
            mPathHorizontal.lineTo(mWidth, y);
        }
        mTotalHeight = y - mStrokeWidth / 2f - mYOffset;

        // 测量竖线
        float x;
        mPathXMap.put(mWeek[0], mXOffset);
        for (int i = 1; i < mWeek.length; i++) {
            x = mStrokeWidth / 2f + mStrokeWidth * (i - 1) + mItemWidth * i + mXOffset;
            if (x > 0) {
                mPathVertical.moveTo(x, 0);
                mPathVertical.lineTo(x, Math.min(y, mHeight));
            }
            // 位置的记录，则还是要记录第一根线偏移之后的位置，其它基于此位置的才能绘制正确
            mPathXMap.put(String.valueOf(mWeek[i]), x);
        }
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);

        // 绘制工作项的背景
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mBackgroundColor);
        mPaint.setAlpha(mEnableAlpha);
        canvas.drawRect(0, mWeekHeight + mStrokeWidth / 2f, mWidth, mHeight, mPaint);
        // 绘制工作项
        drawWork(canvas);
        mPaint.setAlpha(mEnableAlpha);
        // 盖一层白色背景在周后面，遮住下面的内容
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mAxisBackgroundColor);
        canvas.drawRect(0, 0, mWidth, mWeekHeight, mPaint);
        // 绘制横线
        drawHorizontalAxis(canvas);
        // 绘制竖线
        drawVerticalAxis(canvas);
        // 绘制周
        drawWeek(canvas);
    }

    private void drawHorizontalAxis(Canvas canvas) {
        mPaint.setColor(mLineColor);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(mPathHorizontal, mPaint);
    }

    private void drawVerticalAxis(Canvas canvas) {
        mPaint.setColor(mLineColor);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(mPathVertical, mPaint);
    }

    private void drawWeek(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(mWeekTextSize);
        float x;
        for (int i = mWeek.length - 1; i >= 0; i--) {
            Float lineX = mPathXMap.get(mWeek[i]);
            if (lineX != null)
                x = lineX + mStrokeWidth / 2f + mItemWidth / 2f;
            else
                x = 0;

            float weekWidth = mPaint.measureText(mWeek[i]);

            // 移动到坐标轴的不进行绘制
            if (x < -weekWidth)
                continue;

            mPaint.setColor(mWeekColor);
            canvas.drawText(mWeek[i], x, mWeekMarginVertical + mPaint.getTextSize() * 0.82f, mPaint);
        }
    }

    // setAlpha需要在setColor之后调用才能生效
    private void drawWork(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        List<TimeWork> timeWorks;
        TimeWork timeWork;
        DateInfo dateInfo;
        for (int i = 0; i < mDate.size(); i++) {
            dateInfo = mDate.get(i);
            timeWorks = mWorkMap.get(dateInfo.date);
            Float lineX = mPathXMap.get(mWeek[TimeUtils.getDayOfWeekMonday(dateInfo.date) - 1]);
            Float lineY = mPathYMap.get(i / mWeek.length);
            Float nextLineY = mPathYMap.get(i / mWeek.length + 1);
            if (lineX != null && lineY != null && nextLineY != null) {
                float top = lineY + mStrokeWidth / 2f;
                float left = lineX + mItemPadding + mStrokeWidth / 2f;
                float bottom = nextLineY - mStrokeWidth / 2f;

                boolean isSameMonth = dateInfo.date.startsWith(String.format(Locale.getDefault(),
                        "%02d-%02d", mYear, mMonth));
                // 绘制选中日期的背景
                boolean isSelDate = dateInfo.date.equalsIgnoreCase(String.format(Locale.getDefault(),
                        "%02d-%02d-%02d", mYear, mMonth, mDay));
                if (isSelDate) {
                    mPaint.setColor(mSelBackgroundColor);
                    mPaint.setAlpha(mEnableAlpha);
                    canvas.drawRect(left - mItemPadding, top, left - mItemPadding + mItemWidth, bottom, mPaint);
                }
                // 绘制内容item
                if (timeWorks != null && !timeWorks.isEmpty()) {
                    top += mItemPadding;
                    float tempTop;
                    RectF rectF;
                    for (int j = 0; j < timeWorks.size(); j++) {
                        timeWork = timeWorks.get(j);
                        tempTop = top + (mWorkHeight + mItemPadding) * j;
                        if (timeWork.getRectF() == null)
                            rectF = new RectF();
                        else
                            rectF = timeWork.getRectF();
                        rectF.left = left;
                        rectF.top = tempTop;
                        rectF.right = left + mWorkWidth;
                        rectF.bottom = tempTop + mWorkHeight;
                        timeWork.setRectF(rectF);

                        // 如果超出屏幕可视范围，则不用继续绘制了
                        if (tempTop > mHeight || tempTop < 0) {
                            continue;
                        } else if (left > mWidth || left < -mItemWidth) {
                            continue;
                        }

                        mPaint.setColor(timeWork.getBgColor());
                        mPaint.setTextSize(mWorkTextSize);
                        mPaint.setAlpha(isSameMonth ? mEnableAlpha : mDisableAlpha);
                        // 内容圆角度数
                        int ROUND_ANGLE = 6;
                        canvas.drawRoundRect(rectF, ROUND_ANGLE, ROUND_ANGLE, mPaint);

                        mPaint.setColor(Color.WHITE);
                        float subTitleWidth = 0;
                        String subTitle = timeWork.getSubTitle();
                        float titleY = tempTop + mWorkPaddingVertical + mPaint.getTextSize() * 0.82f;
                        if (!isOnlyShowTitle && isShowSubTitle && !TextUtils.isEmpty(subTitle)) {
                            subTitleWidth = mPaint.measureText(subTitle);
                            float maxWidth = mWorkWidth - 2 * mWorkPaddingHorizontal;
                            boolean isOver = false;
                            while (subTitleWidth > maxWidth && !subTitle.isEmpty()) {
                                isOver = true;
                                subTitle = subTitle.substring(0, subTitle.length() - 1);
                                subTitleWidth = mPaint.measureText(subTitle);
                            }
                            if (isShowEllipsis && isOver && subTitle.length() >= 2) {
                                Object[] res = ellipse(subTitle, maxWidth);
                                subTitle = (String) res[0];
                                subTitleWidth = (float) res[1];
                            }

                            canvas.drawText(subTitle, left + mWorkWidth - mWorkPaddingHorizontal - subTitleWidth / 2f, titleY, mPaint);
                        }

                        String title = timeWork.getTitle();
                        if (title != null) {
                            float titleWidth = mPaint.measureText(title);
                            int maxWidth = (int) (mWorkWidth - 2 * mWorkPaddingHorizontal - subTitleWidth);
                            if (subTitleWidth > 0)
                                maxWidth -= TITLE_INTERVAL_TO_SUB_TITLE;

                            boolean isOver = titleWidth > maxWidth && !title.isEmpty();
                            // 两行的情况下，如果仅展示标题，则需要进行特殊处理，标题切割成两行展示，如果一行足够，则需要显示在中间且字体大一号
                            if (isShowTip && isOnlyShowTitle) {
                                if (isOver) {
                                    // 显示两行
                                    String actTitle;
                                    float actTitleWidth = mPaint.measureText(title.substring(0, 1));
                                    float tempWidth = actTitleWidth;
                                    int index = 1;
                                    while (tempWidth <= maxWidth && index < title.length()) {
                                        actTitleWidth = tempWidth;
                                        tempWidth = mPaint.measureText(title.substring(0, ++index));
                                    }
                                    actTitle = title.substring(0, index - 1);
                                    canvas.drawText(actTitle, left + mWorkPaddingHorizontal + actTitleWidth / 2f, titleY, mPaint);

                                    if (index <= title.length()) {
                                        int startIndex = index - 1;
                                        actTitle = title.substring(startIndex, index);
                                        tempWidth = mPaint.measureText(actTitle);
                                        while (tempWidth <= maxWidth) {
                                            actTitleWidth = tempWidth;
                                            // 超过长度则退出循环
                                            if (index >= title.length())
                                                break;
                                            actTitle = title.substring(startIndex, ++index);
                                            tempWidth = mPaint.measureText(actTitle);
                                        }
                                        if (tempWidth > maxWidth) {
                                            if (isShowEllipsis && index - 2 > startIndex) {
                                                Object[] res = ellipse(actTitle, maxWidth);
                                                actTitle = (String) res[0];
                                                actTitleWidth = (float) res[1];
                                            } else {
                                                actTitle = title.substring(startIndex, index - 1);
                                            }
                                        }
                                        canvas.drawText(actTitle, left + mWorkPaddingHorizontal + actTitleWidth / 2f,
                                                tempTop + mWorkPaddingVertical * 2 + mPaint.getTextSize() * 1.82f, mPaint);
                                    }
                                } else {
                                    // 显示一行
                                    if (mBiggerWorkTextSize != mWorkTextSize) {
                                        mPaint.setTextSize(mBiggerWorkTextSize);
                                        // 如果字体加大之后超过最大宽度了，则切换回原大小
                                        titleWidth = mPaint.measureText(title);
                                        if (titleWidth > maxWidth) {
                                            mPaint.setTextSize(mWorkTextSize);
                                        }
                                    }
                                    canvas.drawText(title, left + mWorkWidth / 2f,
                                            tempTop + (mWorkHeight - mPaint.getTextSize()) / 2 + mPaint.getTextSize() * 0.82f, mPaint);
                                }
                            } else {
                                while (titleWidth > maxWidth && !title.isEmpty()) {
                                    title = title.substring(0, title.length() - 1);
                                    titleWidth = mPaint.measureText(title);
                                }
                                if (isShowEllipsis && isOver && title.length() >= 2) {
                                    Object[] res = ellipse(title, maxWidth);
                                    title = (String) res[0];
                                    titleWidth = (float) res[1];
                                }
                                canvas.drawText(title, left + mWorkPaddingHorizontal + titleWidth / 2f, titleY, mPaint);
                            }
                        }

                        String tip = timeWork.getTip();
                        if (!isOnlyShowTitle && isShowTip && !TextUtils.isEmpty(tip)) {
                            float tipWidth = mPaint.measureText(tip);
                            float maxWidth = mWorkWidth - 2 * mWorkPaddingHorizontal;
                            boolean isOver = false;
                            while (tipWidth > maxWidth && !tip.isEmpty()) {
                                isOver = true;
                                tip = tip.substring(0, tip.length() - 1);
                                tipWidth = mPaint.measureText(tip);
                            }
                            if (isShowEllipsis && isOver && tip.length() >= 2) {
                                Object[] res = ellipse(tip, maxWidth);
                                tip = (String) res[0];
                                tipWidth = (float) res[1];
                            }

                            canvas.drawText(tip, left + mWorkPaddingHorizontal + tipWidth / 2f,
                                    titleY + mWorkPaddingVertical + mPaint.getTextSize(), mPaint);
                        }
                    }
                }

                mPaint.setTextSize(mDateTextSize);
                float maxDateWidth = mPaint.measureText("30");
                float dateX = lineX + mItemWidth - maxDateWidth / 2f - mDateTextMargin;
                float dateY = bottom - mPaint.getTextSize() * 0.18f - mDateTextMargin;
                float arcLeft = lineX + mItemWidth + mStrokeWidth * 1.5f - mCircleRadius;
                float arcTop = bottom + mStrokeWidth - mCircleRadius;
                float arcRight = arcLeft + mCircleRadius * 2;
                float arcBottom = arcTop + mCircleRadius * 2;
                // 绘制日期背景
                if (isSelDate) {
                    // 选中的日期
                    mPaint.setColor(mCircleSelColor);
                } else {
                    if (!isSameMonth) {
                        mPaint.setColor(mBackgroundColor);
                        mPaint.setAlpha(mEnableAlpha);
                        canvas.drawArc(arcLeft, arcTop, arcRight, arcBottom, 180, 90, true, mPaint);
                    }
                    mPaint.setColor(mCircleColor);
                }
                mPaint.setAlpha(isSameMonth ? mEnableAlpha : mDisableAlpha);
                canvas.drawArc(arcLeft, arcTop, arcRight, arcBottom, 180, 90, true, mPaint);

                // 绘制日期背景上部的间隔条
                mPaint.setStyle(Paint.Style.STROKE);
                if (isSelDate)
                    mPaint.setColor(mSelBackgroundColor);
                else
                    mPaint.setColor(Color.WHITE);
                mPaint.setStrokeWidth(4);
                canvas.drawArc(arcLeft - 2, arcTop - 2, arcRight + 2, arcBottom + 2,
                        180, 90, true, mPaint);

                // 绘制日期文本
                mPaint.setStyle(Paint.Style.FILL);
                mPaint.setStrokeWidth(mStrokeWidth);
                if (isSelDate) {
                    mPaint.setColor(Color.WHITE);
                } else {
                    mPaint.setColor(mDateColor);
                }
                mPaint.setAlpha(isSameMonth ? mEnableAlpha : mDisableAlpha);
                canvas.drawText(String.valueOf(dateInfo.day), dateX, dateY, mPaint);
            }
        }
    }

    private Object[] ellipse(String content, float maxWidth) {
        Object[] res = new Object[2];
        content += "...";
        float width = mPaint.measureText(content);
        while (width > maxWidth) {
            content = content.substring(0, content.length() - 4) + "...";
            width = mPaint.measureText(content);
        }
        res[0] = content;
        res[1] = width;
        return res;
    }

    private boolean checkOffset() {
        if (mScrollListener != null)
            mScrollListener.scrollToHorizontalEnd(mXOffset <= mWidth - mTotalWidth);

        Log.i("TimeTable", "mXOffset pre: " + mXOffset + " mYOffset pre: " + mYOffset);
        boolean isOver = false;
        if (mXOffset > 0 || mWidth >= mTotalWidth) {
            mXOffset = 0;
            isOver = true;
        } else if (mXOffset < mWidth - mTotalWidth) {
            mXOffset = mWidth - mTotalWidth;
            isOver = true;
        }

        if (mYOffset > 0 || mHeight >= mTotalHeight) {
            mYOffset = 0;
            isOver = true;
        } else if (mYOffset < mHeight - mTotalHeight) {
            mYOffset = mHeight - mTotalHeight;
            isOver = true;
        }
        Log.i("TimeTable", "mXOffset: " + mXOffset + " mYOffset: " + mYOffset);
        return isOver;
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (gestureDetector.onTouchEvent(event))
            return true;
        else
            return super.onTouchEvent(event);
    }

    @Override
    public boolean onDown(@NonNull MotionEvent e) {
        scroller.forceFinished(true);
        return true;
    }

    @Override
    public void onShowPress(@NonNull MotionEvent e) {

    }

    private String mPreClickDate;

    @Override
    public boolean onSingleTapUp(@NonNull MotionEvent e) {
        if (mClickWorkItemListener == null || scroller.computeScrollOffset()) return true;
        float x = e.getX();
        float y = e.getY();
        if (y > mWeekHeight) {
            int xIndex = 0;
            int yIndex = 0;
            float preX = 0;
            for (int i = 0; i < mWeek.length; i++) {
                Float integer = mPathXMap.get(mWeek[i]);
                if (integer != null) {
                    if (x > preX && x < integer) {
                        break;
                    } else {
                        preX = integer;
                        xIndex = i;
                    }
                }
            }
            float preY = mWeekHeight;
            for (int i = 0; i < mPathYMap.size(); i++) {
                Float integer = mPathYMap.get(i);
                if (integer != null) {
                    if (y > preY && y < integer) {
                        break;
                    } else {
                        preY = integer;
                        yIndex = i;
                    }
                } else
                    break;
            }
            int index = yIndex * 7 + xIndex;
            if (index >= 0 && index < mDate.size()) {
                DateInfo dateInfo = mDate.get(index);
                mPreClickDate = dateInfo.date;
                List<TimeWork> timeWorks = mWorkMap.get(dateInfo.date);
                if (timeWorks != null && !timeWorks.isEmpty()) {
                    TimeWork clickItem = null;
                    RectF rectF;
                    for (TimeWork timeWork : timeWorks) {
                        rectF = timeWork.getRectF();
                        if (rectF != null && rectF.contains(x, y)) {
                            clickItem = timeWork;
                            break;
                        }
                    }
                    mClickWorkItemListener.onClick(dateInfo.date, timeWorks, clickItem);
                } else {
                    mClickWorkItemListener.onClick(dateInfo.date, null, null);
                }
            }
        } else {
            mPreClickDate = null;
        }
        return true;
    }

    public List<TimeWork> getPreClickWorkList() {
        if (!TextUtils.isEmpty(mPreClickDate))
            return mWorkMap.get(mPreClickDate);
        else
            return null;
    }

    @Override
    public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
        mXOffset -= (int) distanceX;
        mYOffset -= (int) distanceY;
        checkOffset();
        refresh();
        return false;
    }

    @Override
    public void onLongPress(@NonNull MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {
        Log.i("TimeTable", "onFling mXOffset: " + mXOffset + " mYOffset: " + mYOffset +
                "  minX: " + (mWidth - mTotalWidth) + " maxX: " + 0 + " velocityX:" + velocityX +
                "  minY: " + (mHeight - mTotalHeight) + " maxY: " + 0 + " velocityY:" + velocityY);
        scroller.fling(
                (int) mXOffset, (int) mYOffset, (int) velocityX, (int) velocityY,
                (int) (mWidth - mTotalWidth), 0,
                (int) (mHeight - mTotalHeight), 0);
        invalidate();
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        Log.i("TimeTable", "currY: " + scroller.getCurrY());
        if (scroller.computeScrollOffset()) {
            mXOffset = scroller.getCurrX();
            mYOffset = scroller.getCurrY();
            checkOffset();
            refresh();
        }
    }
}
